﻿using System;
using System.Collections.Generic;
using System.Linq;

namespace ProLinq.Projection.Configuration
{
	internal class ProjectionConfigurationCache
	{
		#region Nested

		private struct CompositeKey
		{
			private readonly Object[] _keys;

			public CompositeKey(params Object[] keys)
			{
				this._keys = keys;
			}

			public override bool Equals(object obj)
			{
				if (obj == null || obj.GetType() != this.GetType())
					return false;

				var anotherKey = (CompositeKey) obj;

				if (anotherKey._keys.Length != this._keys.Length)
					return false;

				for (int i = 0; i < this._keys.Length; i++)
				{
					if (!this._keys[i].Equals(anotherKey._keys[i]))
						return false;
				}
				return true;
			}

			public override int GetHashCode()
			{
				return this._keys.Select(k => k.GetHashCode()).Aggregate((x, y) => x ^ y);
			}
		}

		#endregion

		#region Fields

		private readonly Dictionary<CompositeKey, Object> _cache = new Dictionary<CompositeKey, Object>();

		#endregion

		#region Methods

		public ProjectionConfiguration<TSource, TResult> GetConfiguration<TSource, TResult>()
		{
			var key = new CompositeKey(typeof (TSource), typeof (TResult));
			return this.GetConfiguration<TSource, TResult>(key);
		}

		public void SetConfiguration<TSource, TResult>(ProjectionConfiguration<TSource, TResult> cfg)
		{
			var key = new CompositeKey(typeof (TSource), typeof (TResult));
			this.SetConfiguration(key, cfg);
		}

		private void SetConfiguration<TSource, TResult>(CompositeKey key, ProjectionConfiguration<TSource, TResult> cfg)
		{
			if (cfg == null)
			{
				this._cache.Remove(key);
			}
			else
			{
				if (this._cache.ContainsKey(key))
				{
					this._cache[key] = cfg;
				}
				else
				{
					this._cache.Add(key, cfg);
				}
			}
		}

		private ProjectionConfiguration<TSource, TResult> GetConfiguration<TSource, TResult>(CompositeKey key)
		{
			Object value = null;
			if (this._cache.TryGetValue(key, out value))
			{
				return (ProjectionConfiguration<TSource, TResult>) value;
			}
			return null;
		}

		#endregion
	}
}